%% Author: wintereagle
%% Created: 2006-8-16
%% Description: TODO: Add desciption to aleph_dht_hash
-module(aleph_dht_hash).
  
%%
%% Include files
%%
-include_lib("eunit/include/eunit.hrl"). 
%%
%% Exported Functions
%% 
-export([new_hash_id/0,
         distance/2,
         random_range/2,
         new_id_in_range/2,
         intify/1,
         new_TID/0]).

%%export unit test
-export([unit_run/0
         ,new_hash_id_LENGTH_test/0,
         distance_KNOWN_test/0,
         distance_COMMUTITIVE_test/0,
         random_range_ONCE_test/0,
         random_range_ONEHUNDRED_test/0]).
-import(aleph_utility,[int_pow/2]).  
%%%%%%%%%%%%%%%%%%%%% Public Functions

%%
%%new_hash_id()
%%
new_hash_id()->  
    crypto:sha_final(crypto:sha_update(crypto:sha_init(),entropy(20))).

intify(Hash_str) when size(Hash_str)==20->
    S1=size(Hash_str)*8,
    <<Int1:S1>> = Hash_str,
    Int1.
   
%%
%%distance(From,To)
%%
distance(From,To)when size(From)==size(To)->
      intify(From) bxor intify(To);
distance(_,_)->
    0.
%%
%%randRange(Min,Max)
%%
random_range(Min,Max)->
        <<New_int_id:160>>=new_hash_id(),
        Min+New_int_id div (Max-Min).  
%%
%%new_id_in_range(Min,Max)
%%
new_id_in_range(Min,Max)->
        erlang:integer_to_list(random_range(Min,Max),16).
        

%%
%%new_TID
%%
new_TID()->
    random_range(int_pow(-2,30),int_pow(2,30)).

%%%%%%%%%%%%%%%%%%%%% private functions
entropy(N)when N<1 ->[];
entropy(N)->lists:foldl(fun(_,Re)->[random:uniform(255)|Re]end,[],lists:seq(1,20)).

   


%%%%%%%%%%%%%%%%%%%%% unit test
new_hash_id_LENGTH_test()->   
    ?match(20,size(new_hash_id())). 

distance_KNOWN_test()->
    B1=list_to_binary(lists:duplicate(20,0)),
    B2=list_to_binary(lists:duplicate(20,16#FF)),
    R1=int_pow(2,160)-1,
    B3=crypto:sha("foo"),
    B4=crypto:sha("bar"),
    Known=[{B1,B2,R1},{B3,B3,0},{B4,B4,0}],
    lists:map(fun({X,Y,Result})->?match(true,distance(X,Y)==Result)end,
                 Known).


distance_COMMUTITIVE_test()->
    lists:foreach(fun(_)-> B1=new_hash_id(),
                       B2=new_hash_id(),
                       B3=new_hash_id(),
                       ?match(true,distance(B1,B2) bxor distance(B2,B3)==distance(B1,B3)) end,
              lists:seq(1,100)).

random_range_ONCE_test()->
    A=intify(new_hash_id()),
    B=intify(new_hash_id()),
    if
       A<B->
           C=random_range(A,B),                     
           ?match(true,(A =< C) and (C < B));
       true ->
           C=random_range(B,A),                     
           ?match(true,(B =< C) and (C< A))
    end.

random_range_ONEHUNDRED_test()->
    lists:foreach(fun(_)->random_range_ONCE_test() end,lists:seq(1,100)).
    
%%    unit test entry
unit_run()->    
    crypto:start(),
    eunit:run([aleph_dht_hash],{suffix,"_test"}). 
        
 
    
